\chapter{预处理}

\section{预处理}

\subsection{宏（Macro）}

宏是一种简单的文本替换工具，可以用于定义一个特定的常量或表达式，一般用大写表示。宏定义使用\#define指令，在编译期间，编译器会将程序中所有的宏替换为其内容。\\

与变量的定义不同的是，宏没有类型，也不占内存空间。\\

\mybox{圆}

\begin{lstlisting}[language=C]
#include <stdio.h>
#define PI 3.14159

double perimeter(double r) {
    return 2 * PI * r;
}

double area(double r) {
    return PI * r * r;
}

int main() {
    double radius;
    printf("Enter radius: ");
    scanf("%lf", &radius);

    printf("Perimeter: %.2f\n", perimeter(radius));
    printf("Area: %.2f\n", area(radius));

    return 0;
}
\end{lstlisting}

\begin{tcolorbox}
    \mybox{运行结果}
    \begin{verbatim}
Enter radius: 5
Perimeter: 31.42
Area: 78.54
	\end{verbatim}
\end{tcolorbox}

宏也可以像函数一样传递参数，但是宏的参数不会进行类型检查，宏最终同样也会在编译期间被展开。\\

但是由于宏定义的内容在编译时会被替换到代码中，有时候会导致运算的优先级发生改变。

\vspace{-0.5cm}

\begin{lstlisting}[language=C]
#define SQUARE(x) x * x
\end{lstlisting}

例如SQUARE(2 + 3)会被展开为2 + 3 * 2 + 3，而不是(2 + 3) * (2 + 3)。因此，最好在宏中使用括号来避免这种情况。

\vspace{-0.5cm}

\begin{lstlisting}[language=C]
#define SQUARE(x) (x) * (x)
\end{lstlisting}

\vspace{0.5cm}

\subsection{条件编译}

条件编译是一种在编译时根据宏的定义来决定是否编译某段代码的方法。\\

\mybox{斐波那契数列}

\begin{lstlisting}[language=C]
#include <stdio.h>

#define RECURSION

#ifdef RECURSION
int fibonacci(int n) {
    if (n == 0) {
        return 0;
    } else if (n == 1) {
        return 1;
    }
    return fibonacci(n - 1) + fibonacci(n - 2);
}
#else
int fibonacci(int n) {
    int seq[n];
    seq[0] = 0;
    seq[1] = 1;

    for (int i = 2; i <= n; i++) {
        seq[i] = seq[i - 1] + seq[i - 2];
    }

    return seq[n];
}
#endif

int main() {
    int n;
    printf("Enter n: ");
    scanf("%d", &n);
    printf("%d\n", fibonacci(n));
    return 0;
}
\end{lstlisting}

\begin{tcolorbox}
    \mybox{运行结果}
    \begin{verbatim}
Enter n: 7
13
	\end{verbatim}
\end{tcolorbox}

\newpage

\section{多文件编译}

\subsection{编译（Compile）}

集成开发环境IDE（Integrated Development Environment）包含了文本编辑器、编译器、调试器和其它工具，可以很方便地进行开发。但是对于大型项目，使用命令行编译更加灵活和高效。\\

\mybox{交换}

\begin{lstlisting}[language=C]
#include <stdio.h>

#define SWAP(a, b) {int t; t = a; a = b; b = t;}

int main() {
    int a = 1;
    int b = 2;

    printf("Before: a = %d, b = %d\n", a, b);
    SWAP(a, b);
    printf("After: a = %d, b = %d\n", a, b);

    return 0;
}
\end{lstlisting}

\vspace{-0.5cm}

\begin{lstlisting}
gcc -Wall swap.c -o swap
./swap
\end{lstlisting}

其中gcc表示编译器的名称，-Wall表示要输出所有警告信息，swap.c为需编译的源文件，-o用于指定输出的可执行文件的名称为swap。编译成功后使用./swap即可运行。\\

一个完整的编译过程包含4个步骤：

\begin{enumerate}
    \item 预处理：将头文件、宏定义等展开
          \vspace{-0.5cm}
          \begin{lstlisting}
gcc -E swap.c -o swap.i
            \end{lstlisting}

    \item 编译：将预处理后的代码转换为汇编代码
          \vspace{-0.5cm}
          \begin{lstlisting}
gcc -S swap.i -o swap.s
            \end{lstlisting}

    \item 汇编：将汇编代码转换为机器码
          \vspace{-0.5cm}
          \begin{lstlisting}
gcc -c swap.s -o swap.o
            \end{lstlisting}

    \item 链接：将目标文件链接为可执行文件
          \vspace{-0.5cm}
          \begin{lstlisting}
gcc swap.o -o swap
            \end{lstlisting}
\end{enumerate}

\vspace{0.5cm}

\subsection{多文件编译}

模块化编程的目的是为了将程序分解成多个独立、可重用的部分。当程序变得复杂时，分成多个文件可以使得程序逻辑更加清晰、易于维护。\\

在多文件中，每个模板一般都分为.h和.c两部分，其中.h文件用于声明函数原型，.c文件用于实现函数。这样其它文件只需要包含.h文件即可使用这些函数，就像包含头文件stdio.h一样，只不过自定义的头文件一般使用双引号包含。\\

由于一个头文件可以被多个源文件包含，为了避免重复定义，一般在头文件的开头使用条件编译来判断是否已经被包含。\\

\mybox{面积}

\begin{lstlisting}[language=C, title=geometry.h]
#ifndef _GEOMETRY_H_
#define _GEOMETRY_H_

double circle_area(double radius);

double triangle_area(double base, double height);

#endif
\end{lstlisting}

\begin{lstlisting}[language=C, title=geometry.c]
#include "geometry.h"

#define PI 3.1415926

double circle_area(double radius) {
    return PI * radius * radius;
}

double triangle_area(double base, double height) {
    return base * height / 2;
}
\end{lstlisting}

\begin{lstlisting}[language=C, title=area.c]
#include <stdio.h>
#include "geometry.h"

int main() {
    printf("Area of circle: %.2f\n", circle_area(5));
    printf("Area of triangle: %.2f\n", triangle_area(5, 10));
    return 0;
}
\end{lstlisting}

\vspace{-0.5cm}

\begin{lstlisting}
gcc -Wall geometry.c area.c -o area
./area
\end{lstlisting}

\begin{tcolorbox}
    \mybox{运行结果}
    \begin{verbatim}
Area of circle: 78.54
Area of triangle: 25.00
	\end{verbatim}
\end{tcolorbox}

\newpage